import { compare, genSalt, hash } from 'bcryptjs'
import * as jwt from 'jsonwebtoken'
import { Application } from 'egg'
import { Sequelize } from 'sequelize'
import { UnitOfWork } from './unit-of-work'
import { Repository } from './repository'

export class Auth {
  app: Application
  model: Sequelize
  user: Repository
  setting: Repository

  constructor(app: Application, uow: UnitOfWork) {
    this.app = app
    this.model = app.model
    this.user = uow.User
    this.setting = uow.Setting
  }

  static async hashPassword(password: string) {
    const rounds = 10
    const saltStr = await genSalt(rounds)
    return hash(password, saltStr)
  }

  async createUser(data: { name: string, password: string }) {
    const { name, password } = data
    if (!password) throw new Error('need password.')

    let existsUser = null
    // if (id) {
    //   try {
    //     existsUser = await this.user.find(id)
    //   } catch (err) { this.app.logger.info(err) }
    // }
    if (!existsUser && name) {
      try {
        existsUser = await this.user.findOne({ where: { name } })
      } catch (err) { this.app.logger.info(err) }
    }
    if (existsUser) return existsUser

    // this.logger.info(getRounds(user.password), getSalt(user.password))
    data.password = await Auth.hashPassword(password)

    return this.user.create(data)
  }

  async updateUser(id, updates) {
    if (updates.password) updates.password = await Auth.hashPassword(updates.password)
    return this.user.update(id, updates)
  }

  async deleteUser(id) {
    // throw new Error({ code: 405, message: `'${this.name}.deleteUser(${id})', can not delete user.` })
    throw new Error(`'${this['name']}.deleteUser(${id})', can not delete user.`)
  }

  async sign(data) {
    const verifyUser = u => {
      if (u === null) return false
      if (u.isLocked) return false
      if (u.expiredAt && u.expiredAt < new Date()) return false
      return true
    }

    const { app } = this
    // app.logger.info('sign %j', data)
    const user = await this.user.findOne({ where: { name: data.username } })
    if (user === null) throw new Error('user not found.')
    const passed = await compare(data.password, user.password)
    // this.logger.info('LoginService compare result', passed)
    if (!passed) throw new Error('user name or password error.')
    if (!verifyUser(user)) throw new Error('verify user error, expired or locked.')
    const signData = { userId: user.id }
    const { jwtSecret, jwtOptions } = app.config.settings
    return jwt.sign(signData, jwtSecret, jwtOptions)
  }

  async decode(accessToken) {
    const { app } = this
    app.logger.info('decode', accessToken)
    const { jwtOptions } = app.config.settings
    const payload = jwt.decode(accessToken, jwtOptions)
    app.logger.info('decode result %j', payload)
    return payload
  }

  async verify(accessToken) {
    const { app } = this
    // app.logger.info('verify', accessToken)
    const { jwtSecret, jwtOptions } = app.config.settings
    return jwt.verify(accessToken, jwtSecret, jwtOptions)
  }

  async getSettings(userId) {
    const { querySettings } = this.app.config.settings
    const globalSettings = await this.setting.list({
      limit: querySettings.max,
      where: { userId: { $eq: null } },
    })
    const userSettings = await this.setting.list({
      limit: querySettings.max,
      where: { userId },
    })
    const currentSettings = {}
    globalSettings.rows.forEach(gs => {
      currentSettings[gs.name] = gs
    })
    userSettings.rows.forEach(us => {
      if (us) currentSettings[us.name] = us
    })
    return currentSettings
  }
}
